/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.zhiqim.git.Git;
import org.zhiqim.git.GitWalker;
import org.zhiqim.git.objer.PackLock;
import org.zhiqim.git.objer.model.PersonIdent;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.objer.objectid.RevCommit;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.refer.Ref;
import org.zhiqim.git.refer.RefUpdateResult;
import org.zhiqim.git.refer.RefUpdater;
import org.zhiqim.git.util.GitStreams;
import org.zhiqim.git.util.GitValidates;

import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Lists;

/**
 * 实现从客户端推送包到服务端处理
 * ca377b809e6fd519380bdd6f291670e88722f9dd cdb763b6376ef4ce4c6fb15dc82dc9330a4d481c refs/heads/master
 * 
 * @version v1.0.0 @author zouzhigang 2017-4-17 新建与整理
 */
public class ReceiveHandler implements HttpHandler
{
    private static final Log log = LogFactory.getLog(ReceiveHandler.class);
    
    private final Git git;
    private final GitWalker walker;
    
    private boolean allowCreates;                   //是否允许创建
    private boolean allowDeletes;                   //是否允许删除
    private boolean allowNonFastForwards;           //是否允许非快前
    private boolean checkReceivedObjects;           //是否验证接收的对象
    
    //请求响应流
    private InputStream input;                      //输入流
    private OutputStream output;                    //输出流，如果支持边频带，会转为SideBandOutputStream
    private OutputStream origOut;                   //原始流
    
    //客户端请求首行功能表
    private Set<String> enabledCapablities;         //客户端请求功能表
    private boolean reportStatus;                  //是否开启状态报告 OPTION_REPORT_STATUS
    private boolean sideBand;                      //是否支持边频带 OPTION_SIDE_BAND_64K

    private List<ReceiveCommand> commands;          //客户端请求命令表
    private Map<String, Ref> refs;                  //展示的引用表
    
    //传入参数
    private PersonIdent ident;                      //验证身份
    private ReceiveHook receiveHook;                 //接收包回调接口

    private ReceiveReader reader;
    private Throwable unpackError;                  //解包错误

    private PackLock packLock;                       //锁定接收包文件

    /** 构建接收到对象 */
    public ReceiveHandler(Git git)
    {
        this.git = git;
        this.walker = new GitWalker(git);

        this.allowCreates = true;
        this.allowDeletes = !git.getConfig().isReceiveDenyDeletes();
        this.allowNonFastForwards = !git.getConfig().isReceiveDenyNonFastForwards();
        this.checkReceivedObjects = git.getConfig().isReceiveFsckObjects();
        
        this.receiveHook = ReceiveHookEmpty.getInstance();
    }
    
    public String getContentType()
    {
        return RECEIVE_PACK_RESULT_TYPE;
    }
    
    public boolean hasSideBand()
    {
        return sideBand;
    }
    
    /******************************************************************************/
    // 设置参数
    /******************************************************************************/

    public void setAllowNonFastForwards(boolean canRewind)
    {
        this.allowNonFastForwards = canRewind;
    }

    public void setCheckReceivedObjects(boolean check)
    {
        this.checkReceivedObjects = check;
    }

    public void setAllowCreates(boolean canCreate)
    {
        this.allowCreates = canCreate;
    }
    
    public void setAllowDeletes(boolean canDelete)
    {
        this.allowDeletes = canDelete;
    }
    
    public void setPersonIdent(PersonIdent ident)
    {
        this.ident = ident;
    }
    
    public void setReceiveHook(ReceiveHook hook)
    {
        this.receiveHook = (hook != null)?hook:ReceiveHookEmpty.getInstance();
    }
    
    /******************************************************************************/
    // 获取参数
    /******************************************************************************/

    public boolean isAllowCreates()
    {
        return allowCreates;
    }
    
    public boolean isAllowDeletes()
    {
        return allowDeletes;
    }

    public boolean isCheckReceivedObjects()
    {
        return checkReceivedObjects;
    }

    public boolean isAllowNonFastForwards()
    {
        return allowNonFastForwards;
    }
    
    public PersonIdent getRefLogIdent()
    {
        return ident;
    }

    public ReceiveHook getReceiveHook()
    {
        return receiveHook;
    }

    /******************************************************************************/
    // 运行时获取参数
    /******************************************************************************/
    
    public List<ReceiveCommand> getAllCommands()
    {
        return Collections.unmodifiableList(commands);
    }
    
    /** 是否有接受包 */
    private boolean needPack()
    {
        for (final ReceiveCommand cmd : commands)
        {
            if (cmd.getType() != ReceiveCommandType.DELETE)
                return true;
        }
        
        return false;
    }
    
    /** 获取指定结果的命令表 */
    private List<ReceiveCommand> getWantCommands(ReceiveCommandResult want)
    {
        ArrayList<ReceiveCommand> list = new ArrayList<>(commands.size());
        for (final ReceiveCommand cmd : commands)
        {
            if (cmd.getResult() == want)
                list.add(cmd);
        }
        
        return Lists.trim(list);
    }
    
    /******************************************************************************/
    // 数据处理
    /******************************************************************************/
    
    /**
     * 处理接受数据
     * 
     * @param input         输入流
     * @param output        输出流
     * @param messages      消息流
     * @throws IOException  IO异常
     */
    public void execute(InputStream input, OutputStream output) throws IOException
    {
        //1.初始化参数
        this.input = input;
        this.output = output;
        this.origOut = output;

        this.enabledCapablities = new HashSet<String>();
        this.commands = new ArrayList<ReceiveCommand>();

        this.refs = git.getRefMap();
        
        try
        {
            //2.解析功能和命令
            this.parseCapablitieAndCommand();
            if (commands.isEmpty())
            {//如果命令为空，无请求或协议不支持，不再处理
                return;
            }
            
            if (sideBand)
            {//如果支持边频带，则使用边频带流
                this.output = new SideBandOutputStream(SB_CH_DATA, SB_MAX_BUF, output);
            }
            
            //3.回调命令列表
            receiveHook.onPreReceive(this, commands);

            //4.接受包
            receivePack();
            
            //5.如果没有错误，验证和执行命令
            if (unpackError == null)
            {
                validateCommands();
                executeCommands();
            }
            
            //6.解锁包
            unlockPack();

            //7.支持发送状态报告到客户端
            if (reportStatus)
            {
                sendStatusReport();
                GitStreams.writeEnd(this.output);
            }

            receiveHook.onPostReceive(this, getWantCommands(ReceiveCommandResult.OK));
        }
        finally
        {
            try
            {
                if (sideBand)
                {//边频带需要刷新Buffer，并写入结束标志
                    ((SideBandOutputStream)this.output).flushBuffer();
                    GitStreams.writeEnd(this.origOut); 
                }
            }
            finally
            {
                unlockPack();
                this.input = null;
                this.output = null;
                this.origOut = null;
                this.refs = null;
                this.enabledCapablities = null;
                this.commands = null;
            }
        }
    }
    
    /** 解析功能和命令 */
    private void parseCapablitieAndCommand() throws IOException
    {
        while (true)
        {
            String line = GitStreams.readString(input);
            if (line == null)
                break;

            if (commands.isEmpty())
            {//首行功能
                int nul = line.indexOf('\0');
                if (nul >= 0)
                {
                    for (String c : line.substring(nul + 1).split(" "))
                        enabledCapablities.add(c);
                    
                    //读取两个重要参数
                    this.reportStatus = enabledCapablities.contains(OPTION_REPORT_STATUS);
                    this.sideBand = enabledCapablities.contains(OPTION_SIDE_BAND_64K);
                    line = line.substring(0, nul);
                }
            }

            //命令格式：${oidId} ${newId} /refs/heads/master
            Asserts.asserts(line.length() > 82, "错误：非法的协议");

            ObjectId oldId = ObjectId.parse(line.substring(0, 40));
            ObjectId newId = ObjectId.parse(line.substring(41, 81));
            String name = line.substring(82);
            ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name);
            if (name.equals(HEAD))
                cmd.setResult(ReceiveCommandResult.REJECTED_CURRENT_BRANCH);
            else
                cmd.setRef(refs.get(cmd.getRefName()));
            
            commands.add(cmd);
        }
    }
    
    
    /** 接受包 */
    private void receivePack()
    {
        if (!needPack())
            return;
        
        try
        {
            reader = new ReceiveReader(git, ident, input, ProgressEmpty.getInstance());
            reader.parse();
    
            String lockMsg = "ZhiqimGit ReceivePack";
            if (getRefLogIdent() != null)
                lockMsg += " from " + getRefLogIdent().toExternalString();
            
            packLock = reader.renameAndOpenPack(lockMsg);
            unpackError = null;
        }
        catch (IllegalArgumentException e) 
        {
            unpackError = e;
            log.error(e.getMessage());
        }
        catch (IOException | RuntimeException | Error e)
        {
            unpackError = e;
            log.error(e);
        }
    }
    
    /** 解锁包 */
    private void unlockPack()
    {
        if (packLock != null)
        {
            packLock.unlock();
            packLock = null;
        }
    }
    
    /******************************************************************************/
    // 数据处理
    /******************************************************************************/
    
    private void validateCommands()
    {
        for (final ReceiveCommand cmd : commands)
        {
            final Ref ref = cmd.getRef();
            if (cmd.getResult() != ReceiveCommandResult.NOT_ATTEMPTED)
                continue;

            if (cmd.getType() == ReceiveCommandType.DELETE && !isAllowDeletes())
            {//拒绝删除
                cmd.setResult(ReceiveCommandResult.REJECTED_NODELETE);
                continue;
            }

            if (cmd.getType() == ReceiveCommandType.CREATE)
            {
                if (!isAllowCreates())
                {//拒绝添加
                    cmd.setResult(ReceiveCommandResult.REJECTED_NOCREATE);
                    continue;
                }

                if (ref != null && !isAllowNonFastForwards())
                {//不支持非快进
                    cmd.setResult(ReceiveCommandResult.REJECTED_NONFASTFORWARD);
                    continue;
                }

                if (ref != null)
                {//参数错误，要求创建引用，而又传递了引用信息
                    cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "引用已存在");
                    continue;
                }
            }

            if (cmd.getType() == ReceiveCommandType.DELETE && 
                ref != null && 
                !ZEROID.equals(cmd.getOldId()) && 
                !ref.getObjectId().equals(cmd.getOldId()))
            {//要求删除的引用名称不正确
                cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "非法的原引用对象编号");
                continue;
            }

            if (cmd.getType() == ReceiveCommandType.UPDATE)
            {
                if (ref == null)
                {//更新引用必须提供原引用
                    cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "更新的引用未传入");
                    continue;
                }

                if (!ref.getObjectId().equals(cmd.getOldId()))
                {//更新引用必须提供原引用
                    cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "要求更新的引用不合法");
                    continue;
                }

                // 非快进更新
                RevObject oldObj, newObj;
                try
                {
                    oldObj = walker.parse(cmd.getOldId());
                }
                catch (IOException e)
                {
                    cmd.setResult(ReceiveCommandResult.REJECTED_MISSING_OBJECT, cmd.getOldId().getName());
                    continue;
                }

                try
                {
                    newObj = walker.parse(cmd.getNewId());
                }
                catch (IOException e)
                {
                    cmd.setResult(ReceiveCommandResult.REJECTED_MISSING_OBJECT, cmd.getNewId().getName());
                    continue;
                }

                if (oldObj instanceof RevCommit && newObj instanceof RevCommit)
                {
                    try
                    {
                        if (!walker.isFastward((RevCommit) oldObj, (RevCommit) newObj))
                        {
                            cmd.setType(ReceiveCommandType.UPDATE_NONFASTFORWARD);
                        }
                    }
                    catch (IllegalArgumentException e)
                    {
                        cmd.setResult(ReceiveCommandResult.REJECTED_MISSING_OBJECT, e.getMessage());
                    }
                    catch (IOException e)
                    {
                        cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON);
                    }
                }
                else
                {
                    cmd.setType(ReceiveCommandType.UPDATE_NONFASTFORWARD);
                }
            }

            if (!cmd.getRefName().startsWith(P_REFS_) || !GitValidates.isValidRefName(cmd.getRefName()))
            {
                cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "引用名称不合法");
            }
        }
    }

    private void executeCommands()
    {
        receiveHook.onPreExecute(this, getWantCommands(ReceiveCommandResult.NOT_ATTEMPTED));
        for (final ReceiveCommand cmd : getWantCommands(ReceiveCommandResult.NOT_ATTEMPTED))
        {
            execute(cmd);
        }
    }

    private void execute(ReceiveCommand cmd)
    {
        try
        {
            RefUpdater ru = git.newUpdater(cmd.getRefName());
            ru.setRefLogIdent(getRefLogIdent());
            switch (cmd.getType())
            {
            case DELETE:
                if (!ZEROID.equals(cmd.getOldId()))
                {
                    ru.setExpectedOldObjectId(cmd.getOldId());
                }
                ru.setForceUpdate(true);
                status(cmd, ru.delete(walker));
                break;

            case CREATE:
            case UPDATE:
            case UPDATE_NONFASTFORWARD:
                ru.setForceUpdate(isAllowNonFastForwards());
                ru.setExpectedOldObjectId(cmd.getOldId());
                ru.setNewObjectId(cmd.getNewId());
                ru.setRefLogMessage("push", true);
                status(cmd, ru.update(walker));
                break;
            }
        }
        catch (IOException err)
        {
            cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, "lock error: " + err.getMessage());
        }
    }

    private void status(final ReceiveCommand cmd, final RefUpdateResult result)
    {
        switch (result)
        {
        case NOT_ATTEMPTED:
            cmd.setResult(ReceiveCommandResult.NOT_ATTEMPTED);
            break;

        case LOCK_FAILURE:
        case IO_FAILURE:
            cmd.setResult(ReceiveCommandResult.LOCK_FAILURE);
            break;

        case NO_CHANGE:
        case NEW:
        case FORCED:
        case FAST_FORWARD:
            cmd.setResult(ReceiveCommandResult.OK);
            break;

        case REJECTED:
            cmd.setResult(ReceiveCommandResult.REJECTED_NONFASTFORWARD);
            break;

        case REJECTED_CURRENT_BRANCH:
            cmd.setResult(ReceiveCommandResult.REJECTED_CURRENT_BRANCH);
            break;

        default:
            cmd.setResult(ReceiveCommandResult.REJECTED_OTHER_REASON, result.name());
            break;
        }
    }

    /** 发送状态报告 */
    private void sendStatusReport() throws IOException
    {
        if (unpackError != null)
        {
            sendString("unpack error " + unpackError.getMessage());
            for (final ReceiveCommand cmd : commands)
            {
                sendString("ng " + cmd.getRefName() + " n/a (解包错误)");
            }
            return;
        }

        sendString("unpack ok");//解包成功
        
        for (final ReceiveCommand cmd : commands)
        {
            if (cmd.getResult() == ReceiveCommandResult.OK)
            {
                sendString("ok " + cmd.getRefName());
                continue;
            }

            StringBuilder strb = new StringBuilder();
            strb.append("ng ");
            strb.append(cmd.getRefName());
            strb.append(" ");

            switch (cmd.getResult())
            {
            case NOT_ATTEMPTED:
                strb.append("服务端错误，引用未处理");
                break;

            case REJECTED_NOCREATE:
                strb.append("服务端拒绝创建");
                break;

            case REJECTED_NODELETE:
                strb.append("服务端拒绝删除");
                break;

            case REJECTED_NONFASTFORWARD:
                strb.append("服务端拒绝非快进，合并时，主分枝有变动");
                break;

            case REJECTED_CURRENT_BRANCH:
                strb.append("主分枝不允许删除");
                break;

            case REJECTED_MISSING_OBJECT:
                if (cmd.getMessage() == null)
                    strb.append("部分对象未找到");
                else if (cmd.getMessage().length() == LEN_OBJECT_ID_STRING)
                    strb.append("对象 " + cmd.getMessage() + " 未找到");
                else
                    strb.append(cmd.getMessage());
                break;

            case REJECTED_OTHER_REASON:
                if (cmd.getMessage() == null)
                    strb.append("未知错误");
                else
                    strb.append(cmd.getMessage());
                break;

            case LOCK_FAILURE:
                strb.append("锁定文件失败");
                break;

            case OK:
                continue;
            }
            sendString(strb.toString());
        }
    }

    /** 发送消息 */
    private void sendString(String s) throws IOException
    {
        GitStreams.writeString(output, s + "\n");
    }
}
